﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Reflection;

namespace InformationExtraction
{
    class Extractor
    {
        public static string StripPos(string raw)
        {
            //posPattern: a "/" followd by at least one non white-space character
            Regex posPattern = new Regex(@"/\S+", RegexOptions.CultureInvariant);
            return posPattern.Replace(raw, "");
        }

        private Parser parser;
        private ExtractorProcess[] processes;
        private Dictionary<string, Person> ontology;

        public Extractor(ExtractorProcess[] processes)
        {
            this.parser = new Parser();
            this.processes = processes;
            this.ontology = new Dictionary<string, Person>();
        }

        //--- analysis ---//

        /// <param name="path">path to the text file</param>
        /// <param name="approxNumOfPages">approximated number of pages. used for efficiency considerations.</param>
        /// <returns>total number of milliseconds of the analysis process</returns>
        public long Analyze(string path, bool printProcess = false, int approxNumOfPages = 60000)
        {
            DateTime begin = DateTime.Now;

            //parse
            parser.Parse(path, printProcess, approxNumOfPages);

            //get persons & add to dictionary
            //note: this is where spliting to sentences occur and so it's the slowest part
            Dictionary<Person, Page> personPage = GetPersonPage(printProcess);

            //get details about each person
            ExtractInformation(personPage, printProcess);

            return (DateTime.Now.Ticks - begin.Ticks) / 10000;
        }

        private Dictionary<Person, Page> GetPersonPage(bool printProcess)
        {
            DateTime begin = DateTime.Now;

            if (printProcess)
                Utils.PrintProcessBegin("Finding Persons", "Pages", parser.Pages.Count);

            Dictionary<Person, Page> res = new Dictionary<Person, Page>();
            Regex heShePattern = new Regex(string.Format("^({0}/PRP )|( {0}/PRP )|( {0}/PRP)$", "((he)|(she))"), RegexOptions.IgnoreCase);
            Regex isBorn = new Regex(@"-LRB-.*?\bborn\b.*?-RRB-", RegexOptions.IgnoreCase);
            int i = 0;
            foreach (Page page in parser.Pages)
            {
                // if the first sentece contains 'born' inside bracket
                if (isBorn.IsMatch(page.Sentences[0].PlainText))
                {
                    if (!ontology.Keys.Contains(page.Title))
                    {
                        Person person = new Person();
                        ontology.Add(page.Title, person);
                        res.Add(person, page);
                    }
                }
                else
                {
                    // if the page following 3 sentences contains 'he' or 'she'
                    for (int j = 0; j < 4; j++)
                        if (page.Sentences.Count > j && heShePattern.IsMatch(page.Sentences[j].RawSentence))
                            if (!ontology.Keys.Contains(page.Title))
                            {
                                Person person = new Person();
                                ontology.Add(page.Title, person);
                                res.Add(person, page);
                            }
                }

                if (printProcess)
                    Utils.PrintProgress(++i, parser.Pages.Count);
            }

            if (printProcess)
                Utils.PrintProcessEnd(begin, i, parser.Pages.Count);

            return res;
        }

        private void ExtractInformation(Dictionary<Person, Page> personPage, bool printProcess)
        {
            DateTime begin = DateTime.Now;

            if (printProcess)
                Utils.PrintProcessBegin("Extracting Information", "Persons", personPage.Keys.Count);

            int i = 0;
            foreach (var person in personPage.Keys)
            {
                foreach (var process in processes)
                    process.Execute(person, personPage[person]);

                if (printProcess)
                    Utils.PrintProgress(++i, personPage.Keys.Count);
            }

            if (printProcess)
                Utils.PrintProcessEnd(begin, i, personPage.Keys.Count);
        }

        //--- page getters ---//

        public int NumOfPages()
        {
            return parser.Pages.Count;
        }

        public void PrintPages(int from, int to, bool showRawData = false)
        {
            Console.WriteLine(new string('=', 5) + " Pages " + from + " - " + to + " : " + new string('=', 5));
            Console.WriteLine();

            for (int i = from; i < to; i++)
            {
                //title
                Console.WriteLine((new string('-', 5)) + " " + parser.Pages[i].Title + " " + (new string('-', 5)));
                Console.WriteLine();

                //plain text
                foreach (Sentence s in parser.Pages[i].Sentences)
                    Console.WriteLine(s.PlainText);
                Console.WriteLine();

                //raw data
                if (showRawData)
                    foreach (Sentence s in parser.Pages[i].Sentences)
                        Console.WriteLine(s.RawSentence);
            }
        }

        public void SearchPage(string query, bool showRawData = false)
        {
            int pageIndex;
            bool isInt = int.TryParse(query, out pageIndex);
            if (isInt)
            {
                PrintPages(pageIndex, pageIndex + 1, showRawData);
                return;
            }

            string[] queryParts = query.Split(' ');
            Regex pagePattern = new Regex(string.Format("^({0})$|^(({0}) +)|( +({0}) +)+|( +({0}))$", string.Join("|", queryParts)), RegexOptions.IgnoreCase);

            foreach(var page in parser.Pages)
                if(pagePattern.IsMatch(page.Title))
                {                    
                    Console.WriteLine(new string('=', 5) + " " + page.Title + " " + new string('=', 5)); 
                    Console.WriteLine();
                    Console.WriteLine(page.PlainText);
                    Console.WriteLine();
                    if (showRawData)
                    {
                        Console.WriteLine(page.RawContent);
                        Console.WriteLine();
                    }
                }
        }

        //--- ontology getters ---//

        public int OntologySize()
        {
            return ontology.Count();
        }

        public void PrintOntology()
        {
            PrintOntology(0, ontology.Count);
        }

        public void PrintOntology(int from, int to)
        {
            Console.WriteLine("  First Name  | Middle Name |  Last Name  | Gender  | isAlive |   Birth Date   |  Country  |  Occupation  | Part Of...");
            Console.WriteLine(new string('-', 120));
            //foreach (var p in ontology.Values)
            //    Console.WriteLine(p.ToString());
            for (int i = from; i < to; i++)
                Console.WriteLine(ontology.Keys.ElementAt(i));
        }

        public Person[] SearchPersons(string personName, bool printProcess = false)
        {
            DateTime begin = DateTime.Now;

            string[] nameParts = personName.Split(' ');
            //namePattern:
            // 1. one of the name parts is the only token in the string
            // 2. one of the name parts appear at the beginning of the string with a trailing blank
            // 3. one of the name parts appear in the middle of the string padded with blanks
            // 4. one of the name parts appear in the end of the string with a leading blank
            Regex namePattern = new Regex(string.Format(@"\b({0})+\b", string.Join("|", nameParts)), RegexOptions.IgnoreCase);
            int i = 0;

            if (printProcess)
                Utils.PrintProcessBegin("Querieng Ontology", "Persons", ontology.Count);


            List<Person> exactMatch = new List<Person>();
            List<Person> exactTitleMatch = new List<Person>();
            List<Person> anyMatch = new List<Person>();
            foreach (string key in ontology.Keys)
            {
                if (printProcess)
                    Utils.PrintProgress(++i, ontology.Keys.Count);

                //try to find exact match of the full name
                if (ontology[key].FullName != null && ontology[key].FullName.ToLower() == personName.ToLower())
                    exactMatch.Add(ontology[key]);

                //try to find exact match of the title
                else if (key.ToLower() == personName.ToLower())
                    exactTitleMatch.Add(ontology[key]);

                //try to find any match
                else if (ontology[key].FullName != null && namePattern.IsMatch(ontology[key].FullName))
                    anyMatch.Add(ontology[key]);
            }

            if (printProcess)
                Utils.PrintProcessEnd(begin, i, ontology.Count);

            if (exactMatch.Count > 0)
                return exactMatch.ToArray();
            if(exactTitleMatch.Count > 0)
                return exactTitleMatch.ToArray();
            return anyMatch.ToArray();
        }

        public Person[] SearchPersons(Person person, bool printProcess = false)
        {
            DateTime begin = DateTime.Now;

            int i = 0;

            if (printProcess)
                Utils.PrintProcessBegin("Querieng Ontology", "Persons", ontology.Count);


            PropertyInfo[] props = typeof(Person).GetProperties();

            List<Person> exactMatch = new List<Person>();
            foreach (string key in ontology.Keys)
            {
                if (printProcess)
                    Utils.PrintProgress(++i, ontology.Keys.Count);

                bool add = true;

                foreach (PropertyInfo p in props)
                {
                    if (p.GetValue(person, null) != null)
                    {
                        if (p.Name == "Occupation")
                        {
                            if (person.Occupation.Count > 0)
                                if (!ontology[key].Occupation.Contains(person.Occupation[0]))
                                    add = false;
                        }
                        else if (p.Name == "Birthday")
                        {
                            if (!ontology[key].Birthday.HasValue)
                                add = false;
                            else if (ontology[key].Birthday.Value.Year != person.Birthday.Value.Year)
                                add = false;
                        }
                        else if (!p.GetValue(person, null).Equals(p.GetValue(ontology[key], null)))
                        {
                            add = false;
                        }
                    }
                }

                if (add)
                    exactMatch.Add(ontology[key]);
            }

            if (printProcess)
                Utils.PrintProcessEnd(begin, i, ontology.Count);

           return exactMatch.ToArray();
        }

        public void PrintPersons(Person[] persons)
        {
            if (persons.Count() != 0)
            {
                Console.WriteLine("  First Name  | Middle Name |  Last Name  | Gender  | isAlive |   Birth Date   |  Country   |  Occupation  | Part Of...");
                Console.WriteLine(new string('-', 120));
                foreach (var p in persons)
                    Console.WriteLine(p);
            }
        }
    }
}
